﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;

namespace Kyoh.Lib.Collections
{
    /// <summary>複数の項目に基づいてソートする為の <see cref="IComparer{T}"/> の実装です。</summary>
    /// <typeparam name="T">比較対象のクラスを指定します。</typeparam>
	public class GenericMultiSorter<T> : IComparer<T>
        where T : class
    {
        private class PropertyEqualityComparer : IEqualityComparer<PropertyDescriptor>
        {
            #region IEqualityComparer<PropertyDescriptor> メンバー

            public bool Equals(PropertyDescriptor x, PropertyDescriptor y)
            {
                return string.Equals(x.Name, y.Name);
            }

            public int GetHashCode(PropertyDescriptor obj)
            {
                return obj.Name.GetHashCode();
            }

            #endregion
        }
        private readonly ListedDictionary<PropertyDescriptor, IComparer> _comparers = new ListedDictionary<PropertyDescriptor, IComparer>(new PropertyEqualityComparer());
        private readonly Dictionary<PropertyDescriptor, ListSortDirection> _directions = new Dictionary<PropertyDescriptor, ListSortDirection>();

        /// <summary>比較するプロパティの個数を取得します。</summary>
        public int Count { get { return _comparers.Count; } }

        /// <summary>比較するプロパティを追加します。</summary>
        /// <param name="property">比較対象のプロパティを指定します。</param>
        /// <param name="direction">ソート時の昇順/降順を指定します。</param>
        public void AddIndex(PropertyDescriptor property, ListSortDirection direction)
        {
            if (_comparers.ContainsKey(property))
            {
                _comparers.Remove(property);
                _directions.Remove(property);
            }

            IComparer comparer = null;
            if (typeof(T).IsSubclassOf(property.ComponentType) || typeof(T) == property.ComponentType)
            {
                var t = typeof(Comparer<>);
                var g = t.MakeGenericType(property.PropertyType);
                comparer = g.GetProperty("Default").GetValue(null, null) as IComparer;
            }
            if (comparer != null)
            {
                _comparers.Insert(0, property, comparer);
                _directions.Add(property, direction);
            }

            SortDirection = direction;
            SortProperty = property;
        }

        /// <summary>比較するプロパティを削除します。</summary>
        /// <param name="property">比較対象から削除するプロパティを指定します。</param>
        public void RemoveIndex(PropertyDescriptor property)
        {
            var changed = _comparers.KeyIndexOf(property) == 0;
            _comparers.Remove(property);
            _directions.Remove(property);

            if (!changed)
                return;

            if (Count > 0)
            {
                SortProperty = _comparers[0].Key;
                SortDirection = _directions[SortProperty];
            }
            else
            {
                SortProperty = null;
                SortDirection = ListSortDirection.Ascending;
            }
        }

        /// <summary>ソート時の昇順/降順を取得します。</summary>
        public ListSortDirection SortDirection { get; private set; }

        /// <summary>ソートするプロパティの最初の候補を取得します。</summary>
        public PropertyDescriptor SortProperty { get; private set; }

        /// <summary>二つの <typeparamref name="T"/> オブジェクトを比較します。</summary>
        /// <param name="x">比較対象の1つめのオブジェクト。</param>
        /// <param name="y">比較対象の2つめのオブジェクト。</param>
        /// <returns>比較した結果を <see cref="System.Int32"/> 値で返します。</returns>
		public int Compare(T x, T y)
		{
            if (x == null)
                return y == null ? 0 : -1;
            foreach (var comp in _comparers)
            {
                var px = comp.Key.GetValue(x);
                var py = comp.Key.GetValue(y);
                var diff = (int)comp.Value.Compare(px, py);
                if (_directions[comp.Key] == ListSortDirection.Descending)
                    diff *= -1;
                if (diff != 0)
                    return diff;
            }
            return 0;
		}

        /// <summary>ソート基準のプロパティを空にします。</summary>
        public void Clear()
        {
            _comparers.Clear();
            _directions.Clear();
            SortProperty = null;
            SortDirection = ListSortDirection.Ascending;
        }

        /// <summary>
        /// 現在の <see cref="T:System.Object"/> を表す <see cref="T:System.String"/> を返します。
        /// </summary>
        /// <returns>
        /// 現在の <see cref="T:System.Object"/> を表す <see cref="T:System.String"/>。
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override string ToString()
        {
            if (_directions.Count > 0)
            {
                var str = new StringBuilder();
                foreach (var kv in _directions)
                {
                    str.Append(kv.Key.Name);
                    if (kv.Value == ListSortDirection.Descending)
                        str.Append(' ').Append("DESC");
                    str.Append(',');
                }
                str.Length--;
                return str.ToString();
            }
            return string.Empty;
        }

        /// <summary>ソート順を示す <see cref="ListSortDescriptionCollection"/> を取得します。</summary>
        /// <returns></returns>
        public ListSortDescriptionCollection GetSortDescriptions()
        {
            var descriptions = new ListSortDescription[_directions.Count];
            int i = 0;
            foreach (var prop in _comparers)
                descriptions[i++] = new ListSortDescription(prop.Key, _directions[prop.Key]);
            return new ListSortDescriptionCollection(descriptions);
        }

        /// <summary>現在のソートに使用されているプロパティを表す <see cref="PropertyDescriptor"/> を取得します。</summary>
        /// <returns></returns>
        public PropertyDescriptor GetSortProperty()
        {
            return _comparers != null && _comparers.Count > 0 ? _comparers[0].Key : null;
        }
    }
}
